﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PerCederberg.Grammatica.Runtime;

namespace SafeScript.MetaModel.Statements
{
    public abstract class Operator
    {
        protected internal Node Node;

        public virtual ITypeDef ComputeType(ITypeDef goal, bool needsRef, ErrorReport report)
        {
            return null;
        }

        public abstract int GetPrecedence();

        /// <summary>
        /// Tests for some possibilities of implicit conversions. This can change the operator tree.
        /// </summary>
        /// <param name="op"></param>
        /// <param name="isType"></param>
        /// <param name="goalType"></param>
        /// <param name="report"></param>
        /// <returns></returns>
        protected ITypeDef ConvertTo(ref Operator op, ITypeDef isType, ITypeDef goalType, ErrorReport report)
        {
            ITypeDef g = goalType is InstanceType ? (goalType as InstanceType).Type : goalType;
            ITypeDef i = isType is InstanceType ? (isType as InstanceType).Type : isType;

            // We can convert a function into a binding
            if (g is BindingTypeDef && i is FunctionTypeDef)
            {
                if (!BindingTypeDef.IsEqualTo(i as FunctionTypeDef, g as BindingTypeDef))
                {
                    this.ReportIncompatibleTypes(g, i, report);
                    return null;
                }
                op = new BindingObjectOperator() { Node = op.Node, Operator = op, Type = goalType as BindingTypeDef };
                return g;
            }

            if (!i.CanBeAssignedTo(g))
            {
                this.ReportIncompatibleTypes(g, i, report);
                return null;
            }
            return i;
        }

        protected void ReportIncompatibleTypes(ITypeDef type1, ITypeDef type2, ErrorReport report)
        {
            report.Add(new ErrorEntry(this.Node) { Type = ErrorType.IncompatibleTypes });
        }

        protected void ReportNotAReferenceType(ErrorReport report)
        {
            report.Add(new ErrorEntry(this.Node) { Type = ErrorType.NotAReferenceType });
        }

        protected void ReportNotAnObject(Node node, ErrorReport report)
        {
            report.Add(new ErrorEntry(node) { Type = ErrorType.NotAnObject });
        }

        /// <summary>
        /// If the type is a function, it inserts a function object operator.
        /// This is necessary since the type of "this.foo" is a FunctionTypeDef, but when executing
        /// "var x = this.foo" we must wrap the FunctionTypeDef into a InstanceTypeDef first, because only
        /// instances can be assigned to variables, or passed as function arguments.
        /// </summary>
        /// <param name="op"></param>
        /// <param name="opType"></param>
        /// <param name="report"></param>
        /// <returns></returns>
        protected InstanceType ToInstance(ref Operator op, ITypeDef opType, ErrorReport report)
        {
            if (opType is InstanceType)
                return opType as InstanceType;
            if (opType is FunctionTypeDef)
            {
                InstanceType t = new InstanceType() { Type = opType };
                op = new FunctionObjectOperator() { Function = op, Node = op.Node };
                return t;
            }
            this.ReportNotAnObject(op.Node, report);
            return null;
        }
    }
}
